package com.qlogic.commons.utils.reflect;

import java.io.InputStream;
import java.io.PrintWriter;

/**
 * An RFC 844 or MIME message header. Includes methods for parsing headers from
 * incoming streams, fetching values, setting values, and printing headers. Key
 * values of null are legal: they indicate lines in the header that don't have a
 * valid key, but do have a value (this isn't legal according to the standard,
 * but lines like this are everywhere).
 */
public class MessageHeader {
	private String keys[];

	private String values[];

	private int nkeys;

	public MessageHeader() {
		grow();
	}

	public MessageHeader(InputStream is) throws java.io.IOException {
		parseHeader(is);
	}

	/**
	 * Find the value that corresponds to this key. It finds only the first
	 * occurrence of the key.
	 * 
	 * @param k
	 *            the key to find.
	 * @return null if not found.
	 */
	public String findValue(String k) {
		if (k == null) {
			for (int i = nkeys; --i >= 0;)
				if (keys[i] == null)
					return values[i];
		} else
			for (int i = nkeys; --i >= 0;) {
				if (k.equalsIgnoreCase(keys[i]))
					return values[i];
			}
		return null;
	}

	public String getKey(int n) {
		if (n < 0 || n >= nkeys)
			return null;
		return keys[n];
	}

	public String getValue(int n) {
		if (n < 0 || n >= nkeys)
			return null;
		return values[n];
	}

	/**
	 * Find the next value that corresponds to this key. It finds the first
	 * value that follows v. To iterate over all the values of a key use:
	 * 
	 * <pre>
	 * 		for(String v=h.findValue(k); v!=null; v=h.findNextValue(k, v)) {
	 * 		    ...
	 * 		}
	 * 	
	 * </pre>
	 */
	public String findNextValue(String k, String v) {
		boolean foundV = false;
		if (k == null) {
			for (int i = nkeys; --i >= 0;)
				if (keys[i] == null)
					if (foundV)
						return values[i];
					else if (values[i] == v)
						foundV = true;
		} else
			for (int i = nkeys; --i >= 0;)
				if (k.equalsIgnoreCase(keys[i]))
					if (foundV)
						return values[i];
					else if (values[i] == v)
						foundV = true;
		return null;
	}

	/**
	 * Prints the key-value pairs represented by this header. Also prints the
	 * RFC required blank line at the end. Omits pairs with a null key.
	 */
	public void print(PrintWriter p) {
		for (int i = 0; i < nkeys; i++)
			if (keys[i] != null)
				p.print(keys[i] + (values[i] != null ? ": " + values[i] : "")
						+ "\r\n");
		p.print("\r\n");
		p.flush();
	}

	/**
	 * Adds a key value pair to the end of the header. Duplicates are allowed
	 */
	public void add(String k, String v) {
		grow();
		keys[nkeys] = k;
		values[nkeys] = v;
		nkeys++;
	}

	/**
	 * Prepends a key value pair to the beginning of the header. Duplicates are
	 * allowed
	 */
	public void prepend(String k, String v) {
		grow();
		for (int i = nkeys; i > 0; i--) {
			keys[i] = keys[i - 1];
			values[i] = values[i - 1];
		}
		keys[0] = k;
		values[0] = v;
		nkeys++;
	}

	/**
	 * Overwrite the previous key/val pair at location 'i' with the new k/v. If
	 * the index didn't exist before the key/val is simply tacked onto the end.
	 */

	public void set(int i, String k, String v) {
		grow();
		if (i < 0) {
			return;
		} else if (i > nkeys) {
			add(k, v);
		} else {
			keys[i] = k;
			values[i] = v;
		}
	}

	/** grow the key/value arrays as needed */

	private void grow() {
		if (keys == null || nkeys >= keys.length) {
			String[] nk = new String[nkeys + 4];
			String[] nv = new String[nkeys + 4];
			if (keys != null)
				System.arraycopy(keys, 0, nk, 0, nkeys);
			if (values != null)
				System.arraycopy(values, 0, nv, 0, nkeys);
			keys = nk;
			values = nv;
		}
	}

	/**
	 * Sets the value of a key. If the key already exists in the header, it's
	 * value will be changed. Otherwise a new key/value pair will be added to
	 * the end of the header.
	 */
	public void set(String k, String v) {
		for (int i = nkeys; --i >= 0;)
			if (k.equalsIgnoreCase(keys[i])) {
				values[i] = v;
				return;
			}
		add(k, v);
	}

	/**
	 * Convert a message-id string to canonical form (strips off leading and
	 * trailing <>s)
	 */
	public static String canonicalID(String id) {
		if (id == null)
			return "";
		int st = 0;
		int len = id.length();
		boolean substr = false;
		int c;
		while (st < len && ((c = id.charAt(st)) == '<' || c <= ' ')) {
			st++;
			substr = true;
		}
		while (st < len && ((c = id.charAt(len - 1)) == '>' || c <= ' ')) {
			len--;
			substr = true;
		}
		return substr ? id.substring(st, len) : id;
	}

	/** Parse a MIME header from an input stream. */
	public void parseHeader(InputStream is) throws java.io.IOException {
		nkeys = 0;
		if (is == null)
			return;
		char s[] = new char[10];
		int firstc = is.read();
		while (firstc != '\n' && firstc != '\r' && firstc >= 0) {
			int len = 0;
			int keyend = -1;
			int c;
			boolean inKey = firstc > ' ';
			s[len++] = (char) firstc;
			parseloop: {
				parseloop2: while ((c = is.read()) >= 0) {
					switch (c) {
					case ':':
						if (inKey && len > 0)
							keyend = len;
						inKey = false;
						break;
					case '\t':
						c = ' ';
					case ' ':
						inKey = false;
						break;
					case '\r':
					case '\n':
						firstc = is.read();
						if (c == '\r' && firstc == '\n') {
							firstc = is.read();
							if (firstc == '\r')
								firstc = is.read();
						}
						if (firstc == '\n' || firstc == '\r' || firstc > ' ')
							break parseloop;
						/* continuation */
						continue parseloop2;
					}
					if (len >= s.length) {
						char ns[] = new char[s.length * 2];
						System.arraycopy(s, 0, ns, 0, len);
						s = ns;
					}
					s[len++] = (char) c;
				}
				firstc = -1;
			}
			while (len > 0 && s[len - 1] <= ' ')
				len--;
			String k;
			if (keyend <= 0) {
				k = null;
				keyend = 0;
			} else {
				k = String.copyValueOf(s, 0, keyend);
				if (keyend < len && s[keyend] == ':')
					keyend++;
				while (keyend < len && s[keyend] <= ' ')
					keyend++;
			}
			String v;
			if (keyend >= len)
				v = new String();
			else
				v = String.copyValueOf(s, keyend, len - keyend);
			add(k, v);
		}
	}

	public String toString() {
		String result = super.toString();
		for (int i = 0; i < keys.length; i++) {
			result += "{" + keys[i] + ": " + values[i] + "}";
		}
		return result;
	}
}
